using System.Collections.Generic;
using System.Text;
using NFreeMarker.Template;

namespace NFreeMarker.Core
{
    /**
     * An instruction that processes a list or foreach block
     */

    public sealed class IteratorBlock : TemplateElement // NOTE: Was internal
    {
        private readonly Expression _listExpression;
        private readonly string _indexName;
        private readonly bool _isForEach;

        /**
         * @param listExpression a variable referring to a sequence or collection
         * @param indexName an arbitrary index variable name
         * @param nestedBlock the nestedBlock to iterate over
         */

        internal IteratorBlock(
            Expression listExpression,
            string indexName,
            TemplateElement nestedBlock,
            bool isForEach)
        {
            _listExpression = listExpression;
            _indexName = indexName;
            _isForEach = isForEach;
            NestedBlock = nestedBlock;
        }

        internal override void Accept(Environment env)
        {
            ITemplateModel baseModel = _listExpression.GetAsTemplateModel(env);
            if (baseModel == null)
            {
                if (env.IsClassicCompatible())
                {
                    // Classic behavior of simply ignoring null references.
                    return;
                }
                AssertNonNull(baseModel, _listExpression, env);
            }
            env.Visit(new Context(this, baseModel));
        }

        public override string GetCanonicalForm()
        {
            if (_isForEach)
            {
                var buf = new StringBuilder("<#foreach ");
                buf.Append(_indexName);
                buf.Append(" in ");
                buf.Append(_listExpression.GetCanonicalForm());
                buf.Append(">");
                if (NestedBlock != null)
                {
                    buf.Append(NestedBlock.GetCanonicalForm());
                }
                buf.Append("</#foreach>");
                return buf.ToString();
            }
            else
            {
                var buf = new StringBuilder("<#list ");
                buf.Append(_listExpression.GetCanonicalForm());
                buf.Append(" as ");
                buf.Append(_indexName);
                buf.Append(">");
                if (NestedBlock != null)
                {
                    buf.Append(NestedBlock.GetCanonicalForm());
                }
                buf.Append("</#list>");
                return buf.ToString();
            }
        }

        public override string GetDescription()
        {
            return _isForEach
                       ? "foreach " + _indexName + " in " + _listExpression
                       : "list " + _listExpression + " as " + _indexName;
        }

        /**
         * A helper class that holds the context of the loop.
         */

        internal class Context : ILocalContext
        {
            private bool _hasNext;
            private ITemplateModel _loopVar;
            private int _index;
            private List<string> _variableNames;
            private readonly IteratorBlock _iteratorBlock;
            private readonly ITemplateModel _list;

            internal Context(IteratorBlock iteratorBlock, ITemplateModel list)
            {
                _iteratorBlock = iteratorBlock;
                _list = list;
            }

            internal void RunLoop(Environment env)
            {
                if (_list is ITemplateCollectionModel)
                {
                    var baseListModel = (ITemplateCollectionModel) _list;
                    ITemplateModelIterator it = baseListModel.Iterator();
                    _hasNext = it.HasNext();
                    while (_hasNext)
                    {
                        _loopVar = it.Next();
                        _hasNext = it.HasNext();
                        if (_iteratorBlock.NestedBlock != null)
                        {
                            env.Visit(_iteratorBlock.NestedBlock);
                        }
                        _index++;
                    }
                }
                else if (_list is ITemplateSequenceModel)
                {
                    var tsm = (ITemplateSequenceModel) _list;
                    int size = tsm.Size();
                    for (_index = 0; _index < size; _index++)
                    {
                        _loopVar = tsm.Get(_index);
                        _hasNext = (size > _index + 1);
                        if (_iteratorBlock.NestedBlock != null)
                        {
                            env.Visit(_iteratorBlock.NestedBlock);
                        }
                    }
                }
                else if (env.IsClassicCompatible())
                {
                    _loopVar = _list;
                    if (_iteratorBlock.NestedBlock != null)
                    {
                        env.Visit(_iteratorBlock.NestedBlock);
                    }
                }
                else
                {
                    throw InvalidTypeException(_list, _iteratorBlock._listExpression, env, "collection or sequence");
                }
            }

            public ITemplateModel GetLocalVariable(string name)
            {
                if (name.StartsWith(_iteratorBlock._indexName))
                {
                    switch (name.Length - _iteratorBlock._indexName.Length)
                    {
                        case 0:
                            return _loopVar;
                        case 6:
                            if (name.EndsWith("_index"))
                            {
                                return new SimpleNumber(_index);
                            }
                            break;
                        case 9:
                            if (name.EndsWith("_has_next"))
                            {
                                return _hasNext ? TemplateBooleanModel.True : TemplateBooleanModel.False;
                            }
                            break;
                    }
                }
                return null;
            }

            public IEnumerable<string> GetLocalVariableNames()
            {
                if (_variableNames == null)
                {
                    _variableNames = new List<string>(3)
                    {
                        _iteratorBlock._indexName,
                        _iteratorBlock._indexName + "_index",
                        _iteratorBlock._indexName + "_has_next"
                    };
                }
                return _variableNames;
            }
        }
    }
}